/*====================================================================== 
 *         A globalmem driver as an example of char device drivers   
 *         The initial developer of the original code is Baohua Song 
 *         <author@linuxdriver.cn>. All Rights Reserved. 
 *         modify by wangxinyong 2011.11
 * ======================================================================*/  

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>


#define GLOBALMEM_SIZE 	0x1000
#define MEM_CLEAR 	0x1
#define GLOBALMEM_MAJOR 0

#define GLOBALMEM_IOC_MAGIC 'w'
#define GLOBALMEM_IOCCLEAR  _IO(GLOBALMEM_IOC_MAGIC, 0)

static int globalmem_major = GLOBALMEM_MAJOR;

struct globalmem_dev {
	struct cdev cdev;
	unsigned char mem[GLOBALMEM_SIZE];
	struct semaphore globalmem_sem;
};

struct globalmem_dev *globalmem_devp;
struct class *globalmem_class;
static atomic_t globalmem_available = ATOMIC_INIT(1);

int globalmem_open(struct inode *inode, struct file *filp)
{
	if (!atomic_dec_and_test(&globalmem_available)) {
		atomic_inc(&globalmem_available);
		return -EBUSY;
	}
	filp->private_data = globalmem_devp;
	return 0;
}

int globalmem_release(struct inode *inode, struct file *filp)
{
	
	atomic_inc(&globalmem_available);
	return 0;
}

static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	ssize_t retval;
	unsigned long  pos;

	struct globalmem_dev *dev = filp->private_data;
	pos = *f_pos;
	if (pos >= GLOBALMEM_SIZE)
		return  -ENXIO;
	
	if (pos + count > GLOBALMEM_SIZE)
		count = GLOBALMEM_SIZE - pos;
	
	if (down_interruptible(&globalmem_devp->globalmem_sem)) { //not available return
		return -ERESTARTSYS;
	}
	/*
 	*unsigned long copy_to_user(void __user *to, const void *from, unsigned long count)
 	* */
	if (copy_to_user(buf, (void*)(dev->mem + pos), count)) {
		retval = -EFAULT;
	}
	else {
		*f_pos += count;
		retval = count;
		printk(KERN_INFO "read %d bytes from %ld\n", count, pos);
	}

	up(&globalmem_devp->globalmem_sem);//release globalmem_sem

	return retval;
}

static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	size_t retval;
	unsigned long  pos;
	struct globalmem_dev *dev = filp->private_data;
	pos = *f_pos;
        
	if (pos >= GLOBALMEM_SIZE)
                return  -ENXIO;
        
	if (pos + count > GLOBALMEM_SIZE)
                count = GLOBALMEM_SIZE - pos;
	
	if (down_interruptible(&globalmem_devp->globalmem_sem)) { //not available return
		return -ERESTARTSYS;
	}
	/*
 	*unsigned long copy_from_user(void *to, const void __user *from, unsigned long count)
 	* */
	if (copy_from_user((void*)(dev->mem + pos), buf, count)) {
                retval = -EFAULT;
	}
        else {
                *f_pos += count;
                retval = count;
                printk(KERN_INFO "write %d bytes from %ld\n", count, pos);
        }
	
	up(&globalmem_devp->globalmem_sem);//release globalmem_sem
        
	return retval;
}

static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
{ 
	struct globalmem_dev *dev = filp->private_data;
	
	switch (cmd) {
		case GLOBALMEM_IOCCLEAR:
			if (down_interruptible(&globalmem_devp->globalmem_sem)) { //not available return
				return -ERESTARTSYS;
			}
			memset(dev->mem, 0, GLOBALMEM_SIZE);
			printk(KERN_INFO "globalmem is set to 0\n");
			up(&globalmem_devp->globalmem_sem);//release globalmem_sem
			break;
		default:
			return -ENOTTY;
	}
	return 0;
}

static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret;
	ret = 0;
	switch(orig) {
		case 0:
			if (offset < 0) {
				ret = -EINVAL;
				break;
			}
			if ((unsigned int)offset > GLOBALMEM_SIZE) {
				ret = -EINVAL;
				break;
			}
			filp->f_pos = (unsigned int)offset;
			ret = filp->f_pos;
			break;
		case 1:
			if ((filp->f_pos +offset) > GLOBALMEM_SIZE) {
				ret = -EINVAL;
                                break;
                        }
			if ((filp->f_pos + offset) < 0) {
				ret = -EINVAL;
                                break;
                        }
			filp->f_pos += offset;
			ret = filp->f_pos;
			break;
		default:
			ret = -EINVAL;
			break;
	}
	return ret;
}

static const struct file_operations globalmem_fops = 
{
	.owner = THIS_MODULE,
	.open = globalmem_open,
	.release = globalmem_release,
	.read = globalmem_read,
	.write = globalmem_write,
	.ioctl = globalmem_ioctl,
	.llseek = globalmem_llseek,
};

int globalmem_init(void) 
{
	int result;
	int error;
	dev_t devno;
	result = alloc_chrdev_region(&devno,0,1,"globalmem");
	if (result < 0) {
		return result;
	}
	globalmem_major =MAJOR(devno);
	globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
	if (!globalmem_devp) {
		result = -ENOMEM;
		goto fail_malloc;
	}
	memset(globalmem_devp,0,sizeof(struct globalmem_dev));
	cdev_init(&globalmem_devp->cdev,&globalmem_fops);
	globalmem_devp->cdev.owner = THIS_MODULE;
	globalmem_devp->cdev.ops = &globalmem_fops;
	error = cdev_add(&globalmem_devp->cdev, devno, 1);
	if (error) {
		printk(KERN_NOTICE "Error %d adding globalmem\n", error);
		goto fail_malloc;
	}
	init_MUTEX(&globalmem_devp->globalmem_sem); //semaphore not lock
	//sema_init(&globaomem_devp->globalmem_sem,1);
	globalmem_class =class_create(THIS_MODULE, "globalmem");		
	if(IS_ERR(globalmem_class)) {
        	printk("Error: failed in creating globalmem class.\n");
		return 0;
	}
	device_create(globalmem_class,NULL, devno, NULL,"globalmem");
	return 0;
fail_malloc:
	unregister_chrdev_region(devno,1);
	return result;
}

void globalmem_exit(void)
{
	cdev_del(&globalmem_devp->cdev);
	kfree(globalmem_devp);
	unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
	device_destroy(globalmem_class, MKDEV(globalmem_major, 0));
	class_destroy(globalmem_class);
}		
	
MODULE_AUTHOR("wangxy");
MODULE_LICENSE("GPL");

module_init(globalmem_init);
module_exit(globalmem_exit);
